package FileServer;

import java.io.*;
import java.security.PrivateKey;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


import Share.*;


class ServerHandler extends WaitRequestHandler
{
	PrivateKey key;
	Certificate certificate;
	public ServerHandler(Certificate certificate, PrivateKey key)
	{
		blocking = true;
		this.key = key;
		this.certificate = certificate;
	}
	
	@Override
	public Boolean connectionRecieved(Token token, SecureDataOutputStream out,
			SecureDataInputStream in) {
		try
		{	
			//Transaction Started prior to function call
			File tempFile = new File(String.format("%1$d_%2$s_temp", token.file, token.owner));
			File file = new File(String.format("%1$d_%2$s", token.file, token.owner));
			if(token.type == TokenType.put)
			{
				try
				{
					tempFile.createNewFile();

					if(!tempFile.canWrite())
					{
						out.write(TransmitStatus.serverWriteError.ordinal());
						return true;
					}
					//Transaction Accepted	
					out.write(TransmitStatus.success.ordinal());
					
					DataOutputStream writer = new DataOutputStream(new FileOutputStream(tempFile));
					
					long fileLength = 0;
					try
					{
						//Start hash
						in.startHash();
						//Read File length
						fileLength = in.readLong();
						//Read File
						long remaining = fileLength;
						byte[] buff = new byte[256];
						while(remaining > 0)
						{
							int pull = (int)(remaining % (buff.length + 1));
							in.read(buff, 0, pull);
							writer.write(buff, 0, pull);
							remaining -= pull;
						}
						writer.close();
					}
					catch(IOException error)
					{
						return false;
					}
					finally
					{
						writer.close();
					}
					
					byte[] hash = in.getAndCheckHash();
					if(hash == null)
					{
						out.write(TransmitStatus.badHash.ordinal());
						return true;
					}
					
					byte[] fileKey = new byte[16];					
					System.arraycopy(hash, 0, fileKey, 0, fileKey.length);
					
					file.createNewFile();
					if(!file.canWrite())
					{
						out.write(TransmitStatus.serverWriteError.ordinal());
						return true;
					}
					
					try
					{
						writer = new DataOutputStream(new FileOutputStream(file));
						Util.write(writer, Util.encryptData(fileKey, certificate));
						
						SecureRandom random = new SecureRandom();
						byte[] iv = new byte[16];
						random.nextBytes(iv);
						Util.write(writer, iv);
						
						SecretKeySpec spec = new SecretKeySpec(fileKey, "AES");
						Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
						cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(iv));
						writer = new DataOutputStream (new CipherOutputStream(writer, cipher));
						writer.writeLong(fileLength);
						
						FileInputStream reader = new FileInputStream(tempFile);
						
						byte[] buff = new byte[256];
						int read = reader.read(buff);
						while(read >= 0)
						{
							writer.write(buff, 0, read);
							read = reader.read(buff);
						}
						reader.close();
						writer.close();
					}
					catch(Exception e)
					{
						out.write(TransmitStatus.serverWriteError.ordinal());
						file.delete();
						return true;
					}
				}
				catch(IOException e)
				{
					return false;
				}
				finally
				{
					tempFile.delete();
				}
			}
			else if(token.type == TokenType.get)
			{
				//Transaction started prior to entry
				try
				{
					if(!file.exists() || !file.canRead())
					{
						out.write(TransmitStatus.serverFileNotFound.ordinal());
						return true;
					}
					
					DataInputStream reader = new DataInputStream(new FileInputStream(file));

					byte[] fileKey = Util.decrypt(Util.readByteArray(reader), key);
					byte[] iv = Util.readByteArray(reader);
					
					
					long fileLength = 0;
					try
					{
						SecretKeySpec spec = new SecretKeySpec(fileKey, "AES");
						Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
						cipher.init(Cipher.DECRYPT_MODE, spec, new IvParameterSpec(iv));
						reader = new DataInputStream (new CipherInputStream(reader, cipher));
						
						fileLength = reader.readLong();
					}
					catch (Exception e)
					{
						out.write(TransmitStatus.serverFileSendError.ordinal());
						return true;
					}
					//Transaction Accepted
					out.write(TransmitStatus.success.ordinal());
					
					try
					{
						//Start hash
						out.startHash();
						//Write File Size
						out.writeLong(fileLength);
						//Write File
						byte[] buff = new byte[256];
						int read = reader.read(buff);
						while(read >= 0)
						{
							out.write(buff, 0, read);
							read = reader.read(buff);
						}
						reader.close();
						//Write Hash
						out.writeHash();
					}
					catch (IOException error)
					{
						return false;
					}
				}
				catch(Exception e)
				{
					out.write(TransmitStatus.serverWriteError.ordinal());
					return false;
				}
			}
			//Send Final Report
			out.write(TransmitStatus.success.ordinal());
			
			System.out.format("%1$s %2$s file %3$d on behalf of %4$s.", token.deleg, token.type == TokenType.get ? "got" : "put", token.file, token.owner);
			System.out.println();
			return true;
		}
		catch(IOException error)
		{
			try {
				out.write(TransmitStatus.error.ordinal());
			}
			catch (IOException e)
			{
			}
			return false;
		}
	}

	@Override
	public void handlerError(Exception error) {
		System.out.println("Error Server Closed.");
		
	}

}